package org.ovirt.jenkins;

import hudson.model.TaskListener;
import hudson.model.Hudson;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Listener;
import hudson.slaves.ComputerLauncher;
import hudson.slaves.SlaveComputer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Logger;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.NullInputStream;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.ovirt.jenkins.model.VM;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

public class OVirtComputerLauncher extends ComputerLauncher {

	private final static Logger logger = Logger
			.getLogger(OVirtComputerLauncher.class.getName());

	@DataBoundConstructor
	public OVirtComputerLauncher(final String restApiUrl, final String apiUser,
			final String apiPass, final String agentUser, final String agentPass) {
		super();
		this.restApiUrl = restApiUrl;
		this.apiUser = apiUser;
		this.apiPass = apiPass;
		this.agentUser = agentUser;
		this.agentPass = agentPass;
	}

	private final String restApiUrl;
	private final String apiUser;
	private final String apiPass;

	private final String agentUser;
	private final String agentPass;

	@Override
	public boolean isLaunchSupported() {
		return true;
	}

	@Override
	public void launch(final SlaveComputer computer, final TaskListener listener)
			throws IOException, InterruptedException {
		logger.fine("Launching");
		final OVirtComputer oVirtComputer = (OVirtComputer) computer;
		Hudson.getInstance().getPlugin(OVirtPlugin.class);
		final OVirtService service = getService();
		VM vm = service.getVm(oVirtComputer.getId());
		int cntr = 0;
		while (!(vm.getStatus() == null || StringUtils.equals(vm.getStatus()
				.getState(), "up"))
				&& cntr < 100) {
			service.launch(oVirtComputer.getId());
			Thread.sleep(5000);
			listener.getLogger().println(
					"##\tVm is starting, status is " + vm.getStatus().getState());
			vm = service.getVm(oVirtComputer.getId());
			cntr++;
		}
		listener.getLogger().println(
				"##\tVm status is " + vm.getStatus().getState());

		try {
			JSch jsch = new JSch();
			final String addr = vm.getGuestInfo().getIp().getAddress();
			listener.getLogger().println("##\tssh connectiong to " + addr);
			final Session session = createSession(listener, jsch, addr);

			executeCommand(listener, session, "rm -rf jdk1.6.0_29", true);

			try {
				executeCommand(
						listener,
						session,
						"wget http://download.oracle.com/otn-pub/java/jdk/6u29-b11/jdk-6u29-linux-i586.bin -O jdk.bin", false);
			} catch(IOException e) {
				listener.getLogger().println("##\tWget failed, trying curl...");
				executeCommand(
						listener,
						session,
						"curl -L http://download.oracle.com/otn-pub/java/jdk/6u29-b11/jdk-6u29-linux-i586.bin > jdk.bin", false);
			}

			executeCommand(listener, session, "sh jdk.bin -noregister", false);

			try {
				executeCommand(listener, session, "wget "
						+ Hudson.getInstance().getRootUrl()
						+ "jnlpJars/slave.jar -O slave.jar", false);
			} catch (IOException e) {
				listener.getLogger().println("##\tWget failed, trying curl...");
				executeCommand(
						listener,
						session,
						"curl -L "
								+ Hudson.getInstance().getRootUrl()
								+ "jnlpJars/slave.jar > slave.jar", false);
			}

			executeCommand(listener, session,
					"nohup jdk1.6.0_29/bin/java -jar slave.jar -jnlpUrl "
							+ Hudson.getInstance().getRootUrl() + "computer/"
							+ computer.getName() + "/slave-agent.jnlp &", false);

			computer.setChannel(new NullInputStream(0), new NullOutputStream(),
					listener, new Listener() {
						public void onClosed(final Channel channel, final IOException cause) {
							listener.getLogger().println("##Closing channel to "+computer.getName());
							if(cause != null) {
								
							}
							session.disconnect();
						}
					});

			listener.getLogger().println("##Disconnecting from agent");

		} catch (JSchException e) {
			listener.getLogger().println("##Problem: " + e.getMessage());
			e.printStackTrace(listener.getLogger());
		}

	}

	private OVirtService getService() {
		return new OVirtService(restApiUrl, apiUser, apiPass);
	}

	Session createSession(final TaskListener listener, JSch jsch,
			final String addr) throws JSchException {
		final Session session = jsch.getSession(agentUser, addr);
		session.setDaemonThread(true);
		session.setUserInfo(new UserInfo() {

			public void showMessage(String msg) {
				listener.getLogger().println(msg);
			}

			public boolean promptYesNo(String msg) {
				listener.getLogger().println(msg);
				listener.getLogger().println(
						"##\toVirt jenkins plugin responded Y");
				return true;
			}

			public boolean promptPassword(String msg) {
				listener.getLogger().println(msg);
				return true;
			}

			public boolean promptPassphrase(String msg) {
				listener.getLogger().println(msg);
				return true;
			}

			public String getPassword() {
				return agentPass;
			}

			public String getPassphrase() {
				return null;
			}
		});
		session.connect();
		return session;
	}

	String executeCommand(final TaskListener listener, Session session,
			String command, boolean ignoreError) throws JSchException, IOException {
		listener.getLogger().println("##" + command);
		final StringBuilder builder = new StringBuilder();
		final ChannelExec channel = (ChannelExec) session.openChannel("exec");
		try {
			final OutputStream out = new OutputStream() {

				@Override
				public void write(int b) throws IOException {
					listener.getLogger().append((char) b);
					builder.append((char) b);
				}
			};
			channel.setCommand(command);
			channel.setOutputStream(out);
			channel.setErrStream(out);
			channel.setInputStream(null);
			InputStream in = channel.getInputStream();
			channel.connect();
			IOUtils.copy(in, out);
			if(!ignoreError && channel.getExitStatus() != 0) {
				throw new IOException("Returned error");
			}
			return builder.toString();
		} finally {
			channel.disconnect();
		}
	}

	@Override
	public void afterDisconnect(SlaveComputer computer, TaskListener listener) {
		listener.getLogger().println("## shuting down computer "+computer.getName());
		final OVirtService service = getService();
		try {
			service.shutdown(((OVirtComputer)computer).getId());
		} catch (final IOException e) {
			e.printStackTrace(listener.getLogger());
		}
	}

	@Override
	public void beforeDisconnect(SlaveComputer computer, TaskListener listener) {
		// TODO Auto-generated method stub
		super.beforeDisconnect(computer, listener);
	}

	public String getRestApiUrl() {
		return restApiUrl;
	}

	public String getApiUser() {
		return apiUser;
	}

	public String getApiPass() {
		return apiPass;
	}

	public String getAgentUser() {
		return agentUser;
	}

	public String getAgentPass() {
		return agentPass;
	}

}
